/*
 * $Id: DefaultsList.java 4047 2011-07-19 18:51:12Z kschaefe $
 * 
 * Copyright 2007 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx.plaf;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;

import org.jdesktop.swingx.painter.Painter;
//import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.util.Contract;

/**
 * A specialty "list" for working with UI defaults. Requires adds to be done
 * using key/value pairs. The purpose of this list is to enforce additions as
 * pairs.
 * 
 * @author Karl George Schaefer
 */
@SuppressWarnings("nls")
public final class DefaultsList {
	private List<Object> delegate;

	/**
	 * Creates a {@code DefaultsList}.
	 */
	public DefaultsList() {
		delegate = new ArrayList<Object>();
	}

	/**
	 * Adds a key/value pair to the defaults list. This implementation defers to
	 * {@link #add(Object, Object, boolean)} with {@code enableChecking} set to
	 * {@code true}.
	 * 
	 * @param key
	 *            the key that will be used to query {@code UIDefaults}
	 * @param value
	 *            the value associated with the key
	 * @throws NullPointerException
	 *             if {@code key} is {@code null}
	 * @throws IllegalArgumentException
	 *             if {@code value} is a type that should be a
	 *             {@code UIResource} but is not. For instance, passing in a
	 *             {@code Border} that is not a {@code UIResource} will cause an
	 *             exception. This checking must be enabled.
	 */
	public void add(Object key, Object value) {
		add(key, value, true);
	}

	/**
	 * Adds a key/value pair to the defaults list. A pair with a {@code null}
	 * value is treated specially. A {@code null}-value pair is never added to
	 * the list and, furthermore, if a key/value pair exists in this list with
	 * the same key as the newly added one, it is removed.
	 * 
	 * @param key
	 *            the key that will be used to query {@code UIDefaults}
	 * @param value
	 *            the value associated with the key
	 * @param enableChecking
	 *            if {@code true} then the value is checked to ensure that it is
	 *            a {@code UIResource}, if appropriate
	 * @throws NullPointerException
	 *             if {@code key} is {@code null}
	 * @throws IllegalArgumentException
	 *             if {@code value} is a type that should be a
	 *             {@code UIResource} but is not. For instance, passing in a
	 *             {@code Border} that is not a {@code UIResource} will cause an
	 *             exception. This checking must be enabled.
	 */
	public void add(Object key, Object value, boolean enableChecking) {
		if (enableChecking) {
			asUIResource(value, value + " must be a UIResource");
		}

		if (value == null && delegate.contains(key)) {
			int i = delegate.indexOf(key);

			delegate.remove(i + 1);
			delegate.remove(i);
		} else if (value != null) {
			delegate.add(Contract.asNotNull(key, "key cannot be null"));
			delegate.add(value);
		}
	}

	// TODO move to Contract?
	private static <T> T asUIResource(T value, String message) {
		if (!(value instanceof UIResource)) {
			boolean shouldThrow = false;

			shouldThrow |= value instanceof ActionMap;
			shouldThrow |= value instanceof Border;
			shouldThrow |= value instanceof Color;
			shouldThrow |= value instanceof Dimension;
			shouldThrow |= value instanceof Font;
			shouldThrow |= value instanceof Icon;
			shouldThrow |= value instanceof InputMap;
			shouldThrow |= value instanceof Insets;
			shouldThrow |= value instanceof Painter<?>;
			// FIXME how to handle UIResource testing
			// shouldThrow |= value instanceof StringValue;

			if (shouldThrow) {
				throw new IllegalArgumentException(message);
			}
		}

		return value;
	}

	/**
	 * Gets a copy of this list as an array.
	 * 
	 * @return an array containing all of the key/value pairs added to this list
	 */
	public Object[] toArray() {
		return delegate.toArray();
	}
}
